﻿/* 
 * Rug.Cmd part of Rugland Console Framework
 * 
 * THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
 * EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * Copyright (C) 2008 Phill Tew. All rights reserved.
 * 
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Packaging;
using System.Reflection;
using System.Windows.Forms;

namespace Rug.Cmd
{
    /// <summary>
    /// Provides methods for accessing packaged files
    /// </summary>
    public static class PackageHelper
    {
        #region Private Static Members

        // static locking object
        private static object m_Lock = new object();
        
        // static lookup for cached packages
        private static Dictionary<string, Package> m_Packages = new Dictionary<string, Package>();

        #endregion

        #region Constructor / Deconstructor

        /// <summary>
        /// Static Constructor
        /// </summary>
        static PackageHelper()
        {
            // ensure that the packages will be disposed on exit 
            Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
        }

        static void Application_ApplicationExit(object sender, EventArgs e)
        {
            Dispose();
        }

        #endregion

        #region Path Helpers

        /// <summary>
        /// Resolve a path 
        /// </summary>
        /// <param name="path">the path to resolve</param>
        /// <returns>the resolved path</returns>
        public static string ResolvePath(string path)
        {
            //if (path.StartsWith("~/"))
            //{
            //    throw new NotImplementedException(string.Format("Could not resolve path '{0}'. Relitive paths have not been implemented yet.", path));
            //}

            return path;
        }

        /// <summary>
        /// make a path safe for use as a uri
        /// </summary>
        /// <param name="path">the unsafe path</param>
        /// <returns>a uri safe path</returns>
        public static string MakeUriSafe(string path)
        {
            if (path == null)
                return ""; 

            path = path.Replace('\\', '/');
            path = path.Replace(' ', '-');

            if (!path.StartsWith("/"))
                path = "/" + path; 

            return path;
        }

        #endregion

        #region Get Packages

        /// <summary>
        /// Get embedded package
        /// </summary>
        /// <param name="type">a type in the same assembly and namespace as the package to load</param>
        /// <param name="path">the name of the embeded package</param>
        /// <returns>a package</returns>
        public static Package GetEmbeddedPackage(Type type, string path)
        {
            lock (m_Lock)
            {
                Package package = null;

                Assembly asm = type.Assembly;

                Stream file = asm.GetManifestResourceStream(type, path);
                {
                    package = Package.Open(file, FileMode.Open, FileAccess.Read);
                }

                return package; 
            }
        }

        /// <summary>
        /// Get package from the file system. This method will cache the package for subsiquent calls to GetPackage. Call ReleasePackage to remove from the cache
        /// </summary>
        /// <param name="path">the path of the package in the file system</param>
        /// <param name="create">true if the package sould be created if it does not exist</param>
        /// <param name="access">file access constants</param>
        /// <returns>a package</returns>
        public static Package GetPackage(string path, bool create, FileAccess access)
        {
            string resolvedPath = ResolvePath(path);

            Package package = null;

            if (m_Packages.TryGetValue(resolvedPath, out package))
            {
                return package;
            }

            if (resolvedPath.StartsWith("~/"))
            {
                throw new Exception(string.Format(Strings.Package_ResolveError, resolvedPath));
            }
            else
            {
                lock (m_Lock)
                {
                    FileInfo info = new FileInfo(resolvedPath);

                    if (info.Exists)
                    {
                        package = Package.Open(info.FullName, FileMode.Open, access);

                        m_Packages.Add(resolvedPath, package);

                    }
                    else if (create)
                    {
                        package = ZipPackage.Open(info.FullName, FileMode.CreateNew, access);

                        m_Packages.Add(resolvedPath, package);
                    }
                
                    return package;
                }
            }
        }

        #endregion

        #region Release Packages

        /// <summary>
        /// Release and close a cached package
        /// </summary>
        /// <param name="package">the package to release</param>
        public static void ReleasePackage(Package package)
        {
            lock (m_Lock)
            {
                string key = null;

                foreach (KeyValuePair<string, Package> pair in m_Packages)
                {
                    if (pair.Value == package)
                    {
                        key = pair.Key;
                        break;
                    } 
                }

                if (key != null)
                {
                    package.Close();
                    m_Packages.Remove(key);
                }
            }
        }

        /// <summary>
        /// Release and close a cached package
        /// </summary>
        /// <param name="path">the path of the package to release</param>
        public static void ReleasePackage(string path)
        {
            lock (m_Lock)
            {
                string resolvedPath = ResolvePath(path);

                Package package = null;

                if (m_Packages.TryGetValue(resolvedPath, out package))
                {
                    package.Close();
                    m_Packages.Remove(resolvedPath);
                }
            }
        }

        /// <summary>
        /// Release all cached packages
        /// </summary>
        public static void Dispose()
        {
            lock (m_Lock)
            {
                foreach (KeyValuePair<string, Package> pair in m_Packages)
                {
                    pair.Value.Close();
                }

                m_Packages.Clear();
            }
        }

        #endregion

        #region Add files to package

        /// <summary>
        /// Add a file from the file system to a package
        /// </summary>
        /// <param name="package">the package to add the file too</param>
        /// <param name="uri">the uri to insert the file at within the package</param>
        /// <param name="filePath">the path of the file to be added</param>
        public static void AddFileToPackage(Package package, string uri, string filePath)
        {
            FileInfo info = new FileInfo(filePath);

            string ext = info.Extension;

            string contentType = "text/xml";

            if (ext.Equals(".jpg", StringComparison.InvariantCultureIgnoreCase))
                contentType = "image/jpeg";
            else if (ext.Equals(".bmp", StringComparison.InvariantCultureIgnoreCase))
                contentType = "image/bmp";
            else if (ext.Equals(".png", StringComparison.InvariantCultureIgnoreCase))
                contentType = "image/png";
            else if (ext.Equals(".xml", StringComparison.InvariantCultureIgnoreCase))
                contentType = "text/xml";
            
            AddFileToPackage(package, uri, filePath, contentType);
        }

        /// <summary>
        /// Add a file from the file system to a package
        /// </summary>
        /// <param name="package">the package to add the file too</param>
        /// <param name="uri">the uri to insert the file at within the package</param>
        /// <param name="filePath">the path of the file to be added</param>
        /// <param name="contentType">mime file type</param>
        public static void AddFileToPackage(Package package, string uri, string filePath, string contentType)
        {
            FileInfo info = new FileInfo(filePath);

            uri = MakeUriSafe(uri);

            if (!info.Exists)
            {
                throw new FileNotFoundException(Strings.Package_FileCouldNotBeAdded, filePath);
            }

            Uri uriObject = new Uri(uri, UriKind.Relative);

            string ext = info.Extension;

            if (package.PartExists(uriObject))
            {
                package.DeletePart(uriObject);
            }

            PackagePart packagePart = package.CreatePart(uriObject, contentType, CompressionOption.Maximum);

            // Copy the data to the Resource Part
            using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
                using (Stream compStream = packagePart.GetStream())
                {
                    CopyStream(fileStream, compStream);
                }

                fileStream.Close();
            }
        }

        #endregion

        #region Remove file from Package

        /// <summary>
        /// Remove a file from a package
        /// </summary>
        /// <param name="package">the package to remove the file from</param>
        /// <param name="uri">the uri of the file to remove</param>
        public static void RemoveFileFromPackage(Package package, string uri)
        {
            Uri uriObject = new Uri(uri, UriKind.Relative);

            if (package.PartExists(uriObject))
            {
                package.DeletePart(uriObject);
            }
        }

        #endregion

        #region Rename path

        /// <summary>
        /// Rename path
        /// </summary>
        /// <param name="package">the package that contains the path to rename</param>
        /// <param name="oldUri">the old uri of the file to be renamed</param>
        /// <param name="newUri">the new uil of the file to be renamed</param>
        public static void RenamePath(Package package, string oldUri, string newUri)
        {
            Uri oldUriObject = new Uri(oldUri, UriKind.Relative);
            Uri newUriObject = new Uri(newUri, UriKind.Relative);

            if (package.PartExists(oldUriObject))
            {
                PackagePart oldPart = package.GetPart(oldUriObject);

                PackagePart packagePart = package.CreatePart(newUriObject, oldPart.ContentType, oldPart.CompressionOption);

                using (Stream oldCompStream = oldPart.GetStream())
                {
                    using (Stream newCompStream = packagePart.GetStream())
                    {
                        CopyStream(oldCompStream, newCompStream);
                    }
                }

                package.DeletePart(oldUriObject);
            }

        }

        #endregion

        #region Copy Stream
        
        /// <summary>
        /// Copy from one stream into another
        /// </summary>
        /// <param name="source">source stream</param>
        /// <param name="target">target stream</param>
        public static void CopyStream(Stream source, Stream target)
        {
            const int bufSize = 0x1000;

            byte[] buf = new byte[bufSize];
            int bytesRead = 0;

            while ((bytesRead = source.Read(buf, 0, bufSize)) > 0)
            {
                target.Write(buf, 0, bytesRead);
            }
        }

        #endregion
    }
}
